SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
6.6 KB · 102 lines tsx
Raw Blame History
1import type { Metadata } from 'next';2import Link from 'next/link';3import { notFound } from 'next/navigation';4import { feeScheduleFor } from '@rareindex/valuation';5import { PageHeader } from '@/components/ui/page-header';6import { Card, CardHeader, EmptyState, StatStrip } from '@/components/ui/primitives';7import { LotsIntelCards, LotsIntelTable } from '@/components/market/lots-table';8import { SalesCards, SalesTable } from '@/components/market/sales-table';9import { FeeScheduleTable, FeeConfidenceBadge } from '@/components/market/fee-schedule';10import { getAuctionHouseStats, listAuctionHouses, listHouseResults, listLots } from '@/lib/queries/market-lists';11import { resolveHouse } from '@/lib/auction-house';12import { fmtMoney, fmtNum, fmtPct, fmtRelative } from '@/lib/format';1314export const revalidate = 300;1516export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {17  const { slug } = await params;18  const h = resolveHouse(slug, await listAuctionHouses());19  return h ? { title: `${h.house} — auctions, results & buyer's premium`, description: `${h.house} on RareIndex: open lots with all-in cost vs RIV, recorded results and the buyer's premium schedule used for all-in estimates.` } : { title: 'Auction house' };20}2122export default async function AuctionHousePage({ params }: { params: Promise<{ slug: string }> }) {23  const { slug } = await params;24  const houses = await listAuctionHouses();25  const h = resolveHouse(slug, houses);26  if (!h) notFound();27  const [stats, lots, results] = await Promise.all([getAuctionHouseStats(h.house), listLots({ house: h.house, open: true, limit: 60, sort: 'ending' }), listHouseResults(h.house, 50)]);28  const schedule = feeScheduleFor(h.house);29  return (30    <div>31      <PageHeader32        kicker="Auction house"33        crumbs={[{ label: 'Auctions', href: '/auctions' }, { label: h.house }]}34        title={h.house}35        description="Objective metrics only (§129): lots tracked, results recorded, median and record all-in sale, and the buyer's premium schedule RareIndex uses to turn hammer prices into buyer-pays costs. Sell-through is shown only when the house's results are recorded per lot."36        compact37        meta={38          <>39            <Link href={`/auctions?house=${encodeURIComponent(h.house)}`} className="hover:text-fg">40              Open lots →41            </Link>42            <Link href={`/auctions/calendar?house=${encodeURIComponent(h.house)}`} className="hover:text-fg">43              Calendar →44            </Link>45          </>46        }47      />48      <StatStrip49        className="mb-4"50        items={[51          { label: 'Open lots', value: fmtNum(stats?.openLots ?? h.upcoming), sub: stats ? `${fmtNum(stats.liveLots)} live · ${fmtNum(stats.lotsWithBids)} with bids` : undefined },52          { label: 'Below RIV', value: stats?.lotsAssessed ? fmtNum(stats.lotsBelowRiv) : '—', sub: stats?.lotsAssessed ? `of ${fmtNum(stats.lotsAssessed)} assessed` : 'not assessed yet' },53          { label: 'Results recorded', value: fmtNum(stats?.results ?? 0), sub: stats?.lastResultAt ? `last ${fmtRelative(stats.lastResultAt)}` : undefined },54          { label: 'Median all-in · 1Y', value: stats?.medianAllInUsd365d !== null && stats?.medianAllInUsd365d !== undefined ? fmtMoney(stats.medianAllInUsd365d) : '—', sub: stats?.results365d ? `${fmtNum(stats.results365d)} results` : 'no result in 12 months' },55          { label: 'Record all-in · 1Y', value: stats?.maxAllInUsd365d !== null && stats?.maxAllInUsd365d !== undefined ? fmtMoney(stats.maxAllInUsd365d) : '—' },56          { label: 'Sell-through', value: stats?.sellThrough !== null && stats?.sellThrough !== undefined ? fmtPct(stats.sellThrough, 0, false) : '—', sub: stats?.sellThrough !== null && stats?.sellThrough !== undefined ? 'lots sold / lots ended' : 'not derivable from this source' },57        ]}58      />59      <div className="grid gap-4 xl:grid-cols-[2fr_1fr]">60        <div className="space-y-4">61          <Card className="overflow-hidden">62            <CardHeader title="Open lots" subtitle="All-in = hammer + buyer's premium (schedule below); bid vs RIV gated like listings." action={<Link href={`/auctions?house=${encodeURIComponent(h.house)}`} className="text-muted hover:text-fg">All →</Link>} />63            <div className="md:hidden">64              <LotsIntelCards items={lots} />65              {!lots.length ? <EmptyState title="No open lots" compact /> : null}66            </div>67            <div className="hidden md:block">68              <LotsIntelTable items={lots} showHouse={false} emptyTitle="No open lots" emptyDescription="This house has no live or upcoming lot tracked right now." />69            </div>70          </Card>71          <Card className="overflow-hidden">72            <CardHeader title="Recent results" subtitle={stats?.estimatedPremiumShare !== null && stats?.estimatedPremiumShare !== undefined ? `Recorded sales from this house. Premium estimated on ${fmtPct(stats.estimatedPremiumShare, 0, false)} of results with a fee basis; the rest report the premium as included or none.` : 'Recorded sales from this house; fee basis appears once the fees worker has run.'} />73            <div className="md:hidden">74              <SalesCards items={results} />75            </div>76            <div className="hidden md:block">77              <SalesTable items={results} />78            </div>79            {!results.length ? <EmptyState title="No results recorded" description="Results flow into Sales once a result connector runs for this house." compact /> : null}80          </Card>81        </div>82        <Card className="overflow-hidden">83          <CardHeader title="Buyer's premium" subtitle="Used to compute all-in costs (§35)" />84          <div className="p-4">85            {schedule ? (86              <FeeScheduleTable schedule={schedule} />87            ) : (88              <div className="space-y-2 text-[12px] text-muted">89                <FeeConfidenceBadge confidence="default" />90                <p>No schedule on file for this house. When a result is recorded as hammer-only, RareIndex adds a default 22 % premium and labels the all-in figure “premium added (default)”; when the connector does not state whether the premium is included, the price is shown as recorded with “fees unknown”.</p>91                <p className="text-subtle">92                  Schedules live in <code className="mono-num">data/fees/auction-houses.json</code> — see the <Link href="/methodology#fees" className="underline-offset-2 hover:underline">methodology</Link>.93                </p>94              </div>95            )}96          </div>97        </Card>98      </div>99    </div>100  );101}102